﻿/*
 * Copyright (c) 2014 SkyeTek, Inc.
 * 
 * Author: alex.ulik@skyetek.com
 * Date:   September 5, 2014
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#pragma warning disable 168

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.IO.Ports;
using System.Diagnostics;

namespace SkyeTest_Serial_Demo
{
    class SerialHandler
    {
        /// <summary>
        /// The current Serial Port used by this handler.
        /// </summary>
        private SerialPort ttl = new SerialPort();
       
        /// <summary>
        /// List of the current connected Serial Ports
        /// </summary>
        private string[] ttlPorts;

        // Port Characteristics
        private string ttlPort = "";
        private int ttlBaud = 38400;

        // Data Characteristics
        private int ttlDataBits = 8;
        private Parity ttlParity = Parity.None;
        private StopBits ttlStopBits = StopBits.One;
        private bool isOpen = false;

        // Recieve Data Members
        private string strRecieved = "";
        private bool freshData = false;

        // Constructor
        public SerialHandler()
        {
            this.ttlPorts = SerialPort.GetPortNames();
        }

        /// <summary>
        /// Scans the system for all available serial COM ports.
        /// </summary>
        /// <returns>An array of port names connected to host.</returns>
        public string[] GetComPorts()
        {
            this.ttlPorts = SerialPort.GetPortNames();
            return this.ttlPorts;
        }

        /// <summary>
        /// Opens the named COM port
        /// </summary>
        /// <param name="inputPort">Name of COM port given by GetComPorts()</param>
        /// <returns>True if successful, False otherwise.</returns>
        public bool OpenPort(string inputPort)
        {
            if (this.ttlPorts.Length < 1)
            {
                // No Ports Connected
                Debug.WriteLine("No COM ports connected");
                return false;
            }
            
            if (!this.ttlPorts.Contains(inputPort))
            {
                // Invalid Input
                Debug.WriteLine("Invalid COM port specified: " + inputPort);
                return false;
            }

            // Connect to the specified COM port and return wether it was successful
            this.ttl.PortName = inputPort;
            this.ttlPort = inputPort;
            this.ttl.BaudRate = this.ttlBaud;
            this.ttl.DataBits = this.ttlDataBits;
            this.ttl.Parity = this.ttlParity;
            this.ttl.StopBits = this.ttlStopBits;
            this.ttl.DataReceived += new SerialDataReceivedEventHandler(GotData);

            try
            {
                this.ttl.Open();
            }
            catch (UnauthorizedAccessException ex)
            {
                Debug.WriteLine("ERROR: Cannot access the connected port: " + this.ttlPort);
                Debug.WriteLine("Is there another application using the port?");
                return false;
            }
            catch (Exception ex)
            {
                Debug.WriteLine("ERROR: A general exception occoured. " + this.ttlPort + " was not opened.");
                Debug.WriteLine("Exception: " + ex.Message);
                return false;
            }

            this.isOpen = this.ttl.IsOpen;

            if (this.isOpen)
            {
                Debug.WriteLine("Successfully opened " + this.ttlPort + ".");
                return true;
            }
            else
            {
                Debug.WriteLine("ERROR: Cannot open " + this.ttlPort + ".");
                return false;
            }
        }

        /// <summary>
        /// Closes the currently handled port
        /// </summary>
        /// <returns>True if successful, False otherwise.</returns>
        public bool ClosePort()
        {
            if (!this.ttl.IsOpen)
            {
                Debug.WriteLine("ERROR: Cannot close the unopened port: " + this.ttlPort);
                return false;
            }

            try
            {
                this.ttl.Close();
            }
            catch (Exception ex)
            {

                Debug.WriteLine("ERROR: Cannot close " + this.ttlPort + ".");
                Debug.WriteLine("Exception: " + ex.Message);
                return false;
            }

            this.isOpen = this.ttl.IsOpen;

            if (!this.isOpen)
            {
                Debug.WriteLine(this.ttlPort + " successfully closed.");
                this.ttlPort = "";
                return true;
            }
            else
            {
                Debug.WriteLine("ERROR: " + this.ttlPort + " was not closed.");
                return false;
            }
        }

        /// <summary>
        /// Writes the buffer of data to the COM port.
        /// </summary>
        /// <param name="input">ASCII char array to send to COM port.</param>
        public void Write(char[] input)
        {
            if (!this.isOpen)
            {
                Debug.WriteLine("ERROR: Cannot write to a closed port.");
                return;
            }

            try
            {
                ttl.Write(input, 0, input.Length);
            }
            catch (TimeoutException ex)
            {
                Debug.WriteLine("ERROR: Device Timeout Exception.");
                Debug.WriteLine("Exception: " + ex.Message);
                return;
            }
            catch (Exception ex)
            {
                Debug.WriteLine("ERROR: Unhandled Exception found. Throwing...");
                throw;
            }
        }

        /// <summary>
        /// The delagate function to handle COM data once it arrives to the system. 
        /// </summary>
        /// <param name="sender">COM Object sending request</param>
        /// <param name="e">Data for Event</param>
        private void GotData(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {
            // Here we need to re-cast the SerialPort as it was passed to us as a basic object
            SerialPort sp = (SerialPort)sender;
            this.strRecieved = sp.ReadExisting();
            // Cut out the /r & /n characters
            this.strRecieved = this.strRecieved.Substring(1, this.strRecieved.Length - 3);
            this.freshData = true;
        }

        /// <summary>
        /// Views the Read buffer and marks it stale.
        /// </summary>
        /// <returns>The buffer containing the read data from the COM port</returns>
        public string Read()
        {
            // We allow the system to settle here, sometimes it reads too fast and misses the response.
            System.Threading.Thread.Sleep(100);
            if (!(this.strRecieved.Length > 0))
            {
                Debug.WriteLine("Read buffer empty!");
                return "";
            }
            if (!this.freshData)
            {
                Debug.WriteLine("Read buffer stale!");
                return "";
            }

            Debug.WriteLine("RX Data: " + strRecieved);
            this.freshData = false;
            return strRecieved;
        }

        /// <summary>
        /// Is the data fresh?
        /// </summary>
        /// <returns>True if data has not been read before, False if it has.</returns>
        public bool isFresh()
        {
            return this.freshData;
        }

        /// <summary>
        /// An example function that utilizes the handler functions to grab the firmware version number.
        /// </summary>
        /// <returns>The firmware version number; -1 if not found.</returns>
        public int GetFirmwareVersion()
        {
            if (!ttl.IsOpen)
            {
                Debug.WriteLine("Cannot get version number without open port.");
                return -1;
            }
            
            // Write the serial data to the COM port
            char[] buffer = "\r0000120100010004\r".ToCharArray();
            this.Write(buffer);
            string test = this.Read();
            
            if (!(test.Length > 0))
            {
                Debug.WriteLine("ERROR: No Response from module.");
                return -1;
            }

            return Int32.Parse(test.Substring(8, 8));
        }
    }
}
